package cc.shacocloud.mirage.core.support;

import cc.shacocloud.mirage.bean.BeanFactory;
import cc.shacocloud.mirage.bean.bind.Component;
import cc.shacocloud.mirage.bean.exception.BeanException;
import cc.shacocloud.mirage.core.ApplicationContext;
import cc.shacocloud.mirage.core.ApplicationContextAware;
import cc.shacocloud.mirage.core.ApplicationEvent;
import cc.shacocloud.mirage.core.ApplicationListener;
import cc.shacocloud.mirage.utils.converter.TypeDescriptor;
import io.netty.channel.EventLoopGroup;
import io.vertx.core.*;
import io.vertx.core.datagram.DatagramSocket;
import io.vertx.core.datagram.DatagramSocketOptions;
import io.vertx.core.dns.DnsClient;
import io.vertx.core.dns.DnsClientOptions;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.file.FileSystem;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetServer;
import io.vertx.core.net.NetServerOptions;
import io.vertx.core.shareddata.SharedData;
import io.vertx.core.spi.VerticleFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * {@link ApplicationContext} 的持有者对象，允许在应用初始化成功后，通过静态方法的
 *
 * @author 思追(shaco)
 */
@Component(lazy = false)
public class MirageHolder implements ApplicationContextAware {
    
    private static ApplicationContext applicationContext;
    
    /**
     * 检查是否可以使用
     */
    private static void check() {
        if (Objects.isNull(applicationContext)) {
            throw new IllegalStateException("MirageHolder 还未初始化，无法使用！");
        }
    }
    
    /**
     * 根据对象名称获取对象
     *
     * @param name 对象名称
     * @return T 返回的对象实例
     * @throws BeanException 如果无法获取对象则抛出该例外
     */
    public static <T> @NotNull T getBean(@NotNull String name) throws BeanException {
        check();
        return applicationContext.getBean(name);
    }
    
    /**
     * 根据对象名称和对象类型获取对象
     * <p>
     * 注： 对象名称是唯一的，对象类型只是用来判断是否可以转换，如果不可以将抛出例外 {@link ClassCastException}
     *
     * @param name    对象名称
     * @param classes 对象类型
     * @return T 返回的对象实例
     * @throws BeanException 如果无法获取对象则抛出该例外
     */
    public static <T> @NotNull T getBean(@NotNull String name, @NotNull Class<T> classes) throws BeanException, ClassCastException {
        check();
        return applicationContext.getBean(name, classes);
    }
    
    /**
     * 根据对象类型获取对象
     * <p>
     * 如果该类型指定存在多个实现，并且未指定优先使用的对象，将抛出异常
     *
     * @param classes 对象类型
     * @return T 返回的对象实例
     * @throws BeanException 如果无法获取对象则抛出该例外
     */
    public static <T> @NotNull T getBean(@NotNull Class<T> classes) throws BeanException {
        check();
        return applicationContext.getBean(classes);
    }
    
    /**
     * 判断指定的对象名称是否存在与当前对象工厂中
     *
     * @param name 对象名称
     * @return 存在则返回 true 反之为 false
     */
    public static boolean containsBean(@NotNull String name) {
        check();
        return applicationContext.containsBean(name);
    }
    
    /**
     * 判断指定的对象类型是否存在与当前对象工厂中
     * <p>
     * 包含该对象的子类型
     *
     * @param classes 对象类型
     * @return 存在则返回 true 反之为 false
     */
    public static <T> boolean containsBean(@NotNull Class<T> classes) {
        check();
        return applicationContext.containsBean(classes);
    }
    
    /**
     * 根据指定类型获取当前对象工厂中所有的实现
     * <p>
     * 包含该对象的子类型
     *
     * @param classes 对象类型
     * @return 返回Map，key 为对象的名称，value 为对象的实例
     */
    public static @NotNull <T> Map<String, T> getBeanByType(@NotNull Class<T> classes) {
        check();
        return applicationContext.getBeanByType(classes);
    }
    
    /**
     * 根据对象名称获取对象类型
     *
     * @param name 对象名称
     * @return 对象类型
     * @throws BeanException 如果无法获取对象则抛出该例外
     */
    @NotNull
    public static <T> Class<T> getBeanType(String name) throws BeanException {
        check();
        return applicationContext.getBeanType(name);
    }
    
    /**
     * 获取 bean 对象上的注解
     *
     * @param beanName       对象名称
     * @param annotationType 注解名称
     * @return 对象上的注解
     * @throws BeanException 如果无法获取对象则抛出该例外
     */
    public static <A extends Annotation> @Nullable A findAnnotationOnBean(String beanName, Class<A> annotationType) throws BeanException {
        check();
        return applicationContext.findAnnotationOnBean(beanName, annotationType);
    }
    
    /**
     * 根据对象类型获取对应的对象名称
     * <p>
     * 包含该对象的子类型
     *
     * @param classes 对象类型
     * @return 对象名称
     */
    public static <T> String[] getBeanNamesForType(Class<T> classes) {
        check();
        return applicationContext.getBeanNamesForType(classes);
    }
    
    /**
     * 根据指定类型获取当前对象工厂中所有可以注入的类型
     * <p>
     * 包含该对象的子类型
     *
     * @param classes 对象类型
     * @return 返回Map，key 为对象的名称，value 为对象的实例
     */
    public static @NotNull <T> Map<String, Class<? extends T>> getBeanClassForType(@NotNull Class<T> classes) {
        check();
        return applicationContext.getBeanClassForType(classes);
    }
    
    /**
     * 根据注解获取对应的对象名称
     *
     * @param annotationType 注解类型
     * @return 返回声明该注解的对象名称
     */
    public static <A extends Annotation> String[] getBeanNamesForAnnotation(Class<A> annotationType) {
        check();
        return applicationContext.getBeanNamesForAnnotation(annotationType);
    }
    
    /**
     * 获取当前应用的 Vertx 对象
     */
    public static Vertx getVertx() {
        check();
        return applicationContext.getVertx();
    }
    
    /**
     * 应用名称
     */
    public static String getApplicationName() {
        check();
        return applicationContext.getApplicationName();
    }
    
    /**
     * 获取应用启动的时间戳
     */
    public static long getStartupDate() {
        check();
        return applicationContext.getStartupDate();
    }
    
    /**
     * 获取当前应用内置的对象工厂
     */
    public static @NotNull BeanFactory getBeanFactory() {
        check();
        return applicationContext.getBeanFactory();
    }
    
    public static Context getOrCreateContext() {
        check();
        return applicationContext.getOrCreateContext();
    }
    
    public static NetServer createNetServer(NetServerOptions options) {
        check();
        return applicationContext.createNetServer(options);
    }
    
    public static NetServer createNetServer() {
        check();
        return applicationContext.createNetServer();
    }
    
    public static NetClient createNetClient(NetClientOptions options) {
        check();
        return applicationContext.createNetClient(options);
    }
    
    public static NetClient createNetClient() {
        check();
        return applicationContext.createNetClient();
    }
    
    public static HttpServer createHttpServer(HttpServerOptions options) {
        check();
        return applicationContext.createHttpServer(options);
    }
    
    public static HttpServer createHttpServer() {
        check();
        return applicationContext.createHttpServer();
    }
    
    public static HttpClient createHttpClient(HttpClientOptions options) {
        check();
        return applicationContext.createHttpClient(options);
    }
    
    public static HttpClient createHttpClient() {
        check();
        return applicationContext.createHttpClient();
    }
    
    public static DatagramSocket createDatagramSocket(DatagramSocketOptions options) {
        check();
        return applicationContext.createDatagramSocket(options);
    }
    
    public static DatagramSocket createDatagramSocket() {
        check();
        return applicationContext.createDatagramSocket();
    }
    
    public static FileSystem fileSystem() {
        check();
        return applicationContext.fileSystem();
    }
    
    public static EventBus eventBus() {
        check();
        return applicationContext.eventBus();
    }
    
    public static DnsClient createDnsClient(int port, String host) {
        check();
        return applicationContext.createDnsClient(port, host);
    }
    
    public static DnsClient createDnsClient() {
        check();
        return applicationContext.createDnsClient();
    }
    
    public static DnsClient createDnsClient(DnsClientOptions options) {
        check();
        return applicationContext.createDnsClient(options);
    }
    
    public static SharedData sharedData() {
        check();
        return applicationContext.sharedData();
    }
    
    public static long setTimer(long delay, Handler<Long> handler) {
        check();
        return applicationContext.setTimer(delay, handler);
    }
    
    public static TimeoutStream timerStream(long delay) {
        check();
        return applicationContext.timerStream(delay);
    }
    
    public static long setPeriodic(long delay, Handler<Long> handler) {
        check();
        return applicationContext.setPeriodic(delay, handler);
    }
    
    public static long setPeriodic(long initialDelay, long delay, Handler<Long> handler) {
        check();
        return applicationContext.setPeriodic(initialDelay, delay, handler);
    }
    
    public static TimeoutStream periodicStream(long delay) {
        check();
        return applicationContext.periodicStream(delay);
    }
    
    public static TimeoutStream periodicStream(long initialDelay, long delay) {
        check();
        return applicationContext.periodicStream(initialDelay, delay);
    }
    
    public static boolean cancelTimer(long id) {
        check();
        return applicationContext.cancelTimer(id);
    }
    
    public static void runOnContext(Handler<Void> action) {
        check();
        applicationContext.runOnContext(action);
    }
    
    public static Future<String> deployVerticle(Verticle verticle) {
        check();
        return applicationContext.deployVerticle(verticle);
    }
    
    public static void deployVerticle(Verticle verticle, Handler<AsyncResult<String>> completionHandler) {
        check();
        applicationContext.deployVerticle(verticle, completionHandler);
    }
    
    public static Future<String> deployVerticle(Verticle verticle, DeploymentOptions options) {
        check();
        return applicationContext.deployVerticle(verticle, options);
    }
    
    public static Future<String> deployVerticle(Class<? extends Verticle> verticleClass, DeploymentOptions options) {
        check();
        return applicationContext.deployVerticle(verticleClass, options);
    }
    
    public static Future<String> deployVerticle(Supplier<Verticle> verticleSupplier, DeploymentOptions options) {
        check();
        return applicationContext.deployVerticle(verticleSupplier, options);
    }
    
    public static void deployVerticle(Verticle verticle, DeploymentOptions options, Handler<AsyncResult<String>> completionHandler) {
        check();
        applicationContext.deployVerticle(verticle, options, completionHandler);
    }
    
    public static void deployVerticle(Class<? extends Verticle> verticleClass, DeploymentOptions options, Handler<AsyncResult<String>> completionHandler) {
        check();
        applicationContext.deployVerticle(verticleClass, options, completionHandler);
    }
    
    public static void deployVerticle(Supplier<Verticle> verticleSupplier, DeploymentOptions options, Handler<AsyncResult<String>> completionHandler) {
        check();
        applicationContext.deployVerticle(verticleSupplier, options, completionHandler);
    }
    
    public static Future<String> deployVerticle(String name) {
        check();
        return applicationContext.deployVerticle(name);
    }
    
    public static void deployVerticle(String name, Handler<AsyncResult<String>> completionHandler) {
        check();
        applicationContext.deployVerticle(name, completionHandler);
    }
    
    public static Future<String> deployVerticle(String name, DeploymentOptions options) {
        check();
        return applicationContext.deployVerticle(name, options);
    }
    
    public static void deployVerticle(String name, DeploymentOptions options, Handler<AsyncResult<String>> completionHandler) {
        check();
        applicationContext.deployVerticle(name, options, completionHandler);
    }
    
    public static Future<Void> undeploy(String deploymentID) {
        check();
        return applicationContext.undeploy(deploymentID);
    }
    
    public static void undeploy(String deploymentID, Handler<AsyncResult<Void>> completionHandler) {
        check();
        applicationContext.undeploy(deploymentID, completionHandler);
    }
    
    public static Set<String> deploymentIDs() {
        check();
        return applicationContext.deploymentIDs();
    }
    
    public static void registerVerticleFactory(VerticleFactory factory) {
        check();
        applicationContext.registerVerticleFactory(factory);
    }
    
    public static void unregisterVerticleFactory(VerticleFactory factory) {
        check();
        applicationContext.unregisterVerticleFactory(factory);
    }
    
    public static Set<VerticleFactory> verticleFactories() {
        check();
        return applicationContext.verticleFactories();
    }
    
    public static boolean isClustered() {
        check();
        return applicationContext.isClustered();
    }
    
    public static <T> void executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered, Handler<AsyncResult<T>> asyncResultHandler) {
        check();
        applicationContext.executeBlocking(blockingCodeHandler, ordered, asyncResultHandler);
    }
    
    public static <T> void executeBlocking(Handler<Promise<T>> blockingCodeHandler, Handler<AsyncResult<T>> asyncResultHandler) {
        check();
        applicationContext.executeBlocking(blockingCodeHandler, asyncResultHandler);
    }
    
    public static <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered) {
        check();
        return applicationContext.executeBlocking(blockingCodeHandler, ordered);
    }
    
    public static <T> Future<T> executeBlocking(Handler<Promise<T>> blockingCodeHandler) {
        check();
        return applicationContext.executeBlocking(blockingCodeHandler);
    }
    
    public static EventLoopGroup nettyEventLoopGroup() {
        check();
        return applicationContext.nettyEventLoopGroup();
    }
    
    public static WorkerExecutor createSharedWorkerExecutor(String name) {
        check();
        return applicationContext.createSharedWorkerExecutor(name);
    }
    
    public static WorkerExecutor createSharedWorkerExecutor(String name, int poolSize) {
        check();
        return applicationContext.createSharedWorkerExecutor(name, poolSize);
    }
    
    public static WorkerExecutor createSharedWorkerExecutor(String name, int poolSize, long maxExecuteTime) {
        check();
        return applicationContext.createSharedWorkerExecutor(name, poolSize, maxExecuteTime);
    }
    
    public static WorkerExecutor createSharedWorkerExecutor(String name, int poolSize, long maxExecuteTime, TimeUnit maxExecuteTimeUnit) {
        check();
        return applicationContext.createSharedWorkerExecutor(name, poolSize, maxExecuteTime, maxExecuteTimeUnit);
    }
    
    public static boolean isNativeTransportEnabled() {
        check();
        return applicationContext.isNativeTransportEnabled();
    }
    
    public static Vertx exceptionHandler(Handler<Throwable> handler) {
        check();
        return applicationContext.exceptionHandler(handler);
    }
    
    public static Handler<Throwable> exceptionHandler() {
        check();
        return applicationContext.exceptionHandler();
    }
    
    public static boolean isMetricsEnabled() {
        check();
        return applicationContext.isMetricsEnabled();
    }
    
    /**
     * 添加侦听器以接收所有事件的通知
     *
     * @param listener
     * @see #removeApplicationListener(ApplicationListener)
     * @see #removeApplicationListeners(Predicate)
     */
    public static void addApplicationListener(ApplicationListener<?> listener) {
        check();
        applicationContext.addApplicationListener(listener);
    }
    
    /**
     * 批量添加侦听器以接收所有事件的通知
     *
     * @param listeners
     * @see #removeApplicationListener(ApplicationListener)
     * @see #removeApplicationListeners(Predicate)
     */
    public static void addApplicationListeners(List<ApplicationListener<?>> listeners) {
        check();
        applicationContext.addApplicationListeners(listeners);
    }
    
    /**
     * 从通知列表中删除侦听器
     *
     * @param listener
     * @see #addApplicationListener(ApplicationListener)
     * @see #removeApplicationListeners(Predicate)
     */
    public static void removeApplicationListener(ApplicationListener<?> listener) {
        check();
        applicationContext.removeApplicationListener(listener);
    }
    
    /**
     * 根据 {@link Predicate} 返回的结果删除对应的监听器
     *
     * @param predicate
     * @see #addApplicationListener(ApplicationListener)
     * @see #removeApplicationListener(ApplicationListener)
     */
    public static void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate) {
        check();
        applicationContext.removeApplicationListeners(predicate);
    }
    
    /**
     * 删除所以监听器
     *
     * @see #removeApplicationListeners(Predicate)
     */
    public static void removeAllListeners() {
        check();
        applicationContext.removeAllListeners();
    }
    
    /**
     * 发布多播事件
     *
     * @param event
     */
    public static <E extends ApplicationEvent> CompositeFuture publishEvent(E event) {
        check();
        return applicationContext.publishEvent(event);
    }
    
    /**
     * 返回给定的键是否存在，即 {@link #getProperty(String)} 是否为 null
     *
     * @param key
     */
    public static boolean containsProperty(@NotNull String key) {
        check();
        return applicationContext.containsProperty(key);
    }
    
    /**
     * 返回与给定键关联的属性值，如果无法解析键，则返回 {@code null}
     *
     * @param key 键
     * @see #getProperty(String, Object)
     * @see #getProperty(String, Class)
     * @see #getRequiredProperty(String)
     */
    @Nullable
    public static Object getProperty(@NotNull String key) {
        check();
        return applicationContext.getProperty(key);
    }
    
    /**
     * 返回与给定键关联的属性值，如果无法解析键，则返回 {@code defaultValue}
     *
     * @param key          键
     * @param defaultValue 如果未匹配则返回该默认值
     * @see #getRequiredProperty(String)
     * @see #getProperty(String, Class)
     */
    @NotNull
    public static Object getProperty(@NotNull String key, @NotNull Object defaultValue) {
        check();
        return applicationContext.getProperty(key, defaultValue);
    }
    
    /**
     * 返回与给定键关联的属性值，如果无法解析键，则返回 {@code null}
     *
     * @param key        键
     * @param targetType 值类型，将值转为该类型
     * @see #getRequiredProperty(String, Class)
     */
    public static <T> @Nullable T getProperty(String key, Class<T> targetType) {
        check();
        return applicationContext.getProperty(key, targetType);
    }
    
    /**
     * 返回与给定键关联的属性值，如果无法解析键，则返回 {@code null}
     *
     * @param key            键
     * @param typeDescriptor 类型描述，将值转为该类型
     * @see #getRequiredProperty(String, Class)
     */
    public static <T> @Nullable T getProperty(String key, TypeDescriptor typeDescriptor) {
        check();
        return applicationContext.getProperty(key, typeDescriptor);
    }
    
    /**
     * 返回与给定键关联的属性值，如果无法解析键，则返回 {@code defaultValue}
     *
     * @param key          键
     * @param targetType   值类型，将值转为该类型
     * @param defaultValue 如果未匹配则返回该默认值
     * @see #getRequiredProperty(String, Class)
     */
    public static <T> @NotNull T getProperty(String key, Class<T> targetType, T defaultValue) {
        check();
        return applicationContext.getProperty(key, targetType, defaultValue);
    }
    
    /**
     * 返回与给定键关联的属性值，如果无法解析键，则返回 {@code defaultValue}
     *
     * @param key            键
     * @param typeDescriptor 类型描述，将值转为该类型
     * @param defaultValue   如果未匹配则返回该默认值
     * @see #getRequiredProperty(String, Class)
     */
    public static <T> @NotNull T getProperty(String key, TypeDescriptor typeDescriptor, T defaultValue) {
        check();
        return applicationContext.getProperty(key, typeDescriptor, defaultValue);
    }
    
    /**
     * 返回与给定键关联的属性值
     *
     * @param key
     * @throws IllegalStateException 如果属性键不存在则抛出例外
     * @see #getRequiredProperty(String, Class)
     */
    @NotNull
    public static Object getRequiredProperty(String key) throws IllegalStateException {
        check();
        return applicationContext.getRequiredProperty(key);
    }
    
    /**
     * 返回与给定键关联的属性值
     *
     * @param key
     * @param targetType
     * @throws IllegalStateException 如果属性键不存在则抛出例外
     */
    public static <T> @NotNull T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
        check();
        return applicationContext.getRequiredProperty(key, targetType);
    }
    
    /**
     * 返回与给定键关联的属性值
     *
     * @param key
     * @param typeDescriptor
     * @throws IllegalStateException 如果属性键不存在则抛出例外
     */
    public static <T> @NotNull T getRequiredProperty(String key, TypeDescriptor typeDescriptor) throws IllegalStateException {
        check();
        return applicationContext.getRequiredProperty(key, typeDescriptor);
    }
    
    /**
     * 解析给定文本中的 {@code ${xxx}} 占位符，将它们替换为由 {@link #getProperty(String)} 解析的相应属性值，
     * 没有默认值的不可解析占位符将被忽略并原封不动地传递
     *
     * @param text 要解析的字符串
     * @return 解析的字符串
     * @see #resolveRequiredPlaceholders
     */
    @NotNull
    public static String resolvePlaceholders(@NotNull String text) {
        check();
        return applicationContext.resolvePlaceholders(text);
    }
    
    /**
     * 解析给定文本中的 {@code ${xxx}} 占位符，将它们替换为由 {@link #getRequiredProperty(String)} 解析的相应属性值，
     * * 没有默认值的不可解析占位符将抛出例外
     *
     * @param text
     * @return 解析的字符串
     * @throws IllegalArgumentException 如果文本中的占位符不存在则抛出例外
     */
    @NotNull
    public static String resolveRequiredPlaceholders(@NotNull String text) throws IllegalArgumentException {
        check();
        return applicationContext.resolveRequiredPlaceholders(text);
    }
    
    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) {
        MirageHolder.applicationContext = applicationContext;
    }
}
